package org.haptimap.hcimodules.gesture;

import org.haptimap.hcimodules.HCIModule;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;

/**
 * <p>The class <code>ShakeNotifier</code> is a {@link HCIModule} which registers 
 * if the user shakes the device and notifies about the action via 
 * the {@link ShakeNotifierListener}.
 * 
 * @see HCIModule
 * 
 * @author Miguel Molina, October 2011
 */

public class ShakeNotifier extends HCIModule implements SensorEventListener {

	/**
	 * The force limit reached to fire an event.
	 */
	private static final double FORCE_THRESHOLD = 1.2;
	private static final int TIME_THRESHOLD = 200;
	private static final int SHAKE_TIMEOUT = 500;
	private static final int SHAKE_DURATION = 1000;
	private static final int SHAKE_COUNT = 2;

	private ShakeNotifierListener mShakeNotifierListener;
	private SensorManager mSensorManager;
	
	private long mLastTime;
	private int shakeCount = 0;
	private long mLastShake;
	//Accelerometer values
	private double x, y, z;
	private double currentValue;
	//Whether the shake is started 
	private boolean shakeStarted = false;
	private boolean sensorsRegistered = false;

	/**
	 * 
	 * @param context The context of the activity calling the shaker.
	 */
	public ShakeNotifier(Context context) {
		super(context);
	}

	@Override
	public void onStart() {
		if (!sensorsRegistered) {
			sensorsRegistered = registerSensors();
		}
	}

	@Override
	public void onPause() {
		if (sensorsRegistered) {
			unregisterSensors();
			sensorsRegistered = false;
		}
	}

	@Override
	public void onResume() {
		if (!sensorsRegistered) {
			sensorsRegistered = registerSensors();
		}
	}

	@Override
	public void onStop() {
		if (sensorsRegistered) {
			unregisterSensors();
			sensorsRegistered = false;
		}
	}

	@Override
	public void onDestroy() {
		if (sensorsRegistered) {
			unregisterSensors();
			sensorsRegistered = false;
		}
	}

	/**
	 * Resumes the shakers listener and register the sensors in order to
	 * acquire data from the accelerometer.
	 * 
	 * @return whether the sensor is successfully enabled
	 */
	private boolean registerSensors() {
		mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
		return mSensorManager.registerListener(this,
				mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
				SensorManager.SENSOR_DELAY_FASTEST);
	}

	/**
	 * Puts the shaker on pause and unregister the sensors. This method is
	 * required at pause, finish or destroy, otherwise the sensors will continue registering
	 * motions and will consume battery power.
	 */
	private void unregisterSensors() {
		if (mSensorManager != null) {
			mSensorManager.unregisterListener(this,
					mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER));
			mSensorManager = null;
		}
		mHandler.removeCallbacks(resetShakeCount);
	}

	/**
	 * Registers the listener that will give feedback about the events
	 *  
	 * @param listener The listener that registers events
	 */
	public void setOnShakeListener(ShakeNotifierListener listener) {
		mShakeNotifierListener = listener;
	}

	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// TODO Auto-generated method stub
	}

	public void onSensorChanged(SensorEvent event) {

		switch (event.sensor.getType()) {

		case Sensor.TYPE_ACCELEROMETER:

			x = event.values[0];
			y = event.values[1];
			z = event.values[2];
			currentValue = Math.round(Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2)));
			double force = (currentValue - SensorManager.STANDARD_GRAVITY)
			/ SensorManager.STANDARD_GRAVITY;
			long now = System.currentTimeMillis();
			
			if ((now - mLastTime) > TIME_THRESHOLD) {
				if (force > FORCE_THRESHOLD) {
					if (!shakeStarted) {
						mHandler.postDelayed(resetShakeCount, SHAKE_TIMEOUT);
						shakeStarted = true;
					}
					if ((now - mLastShake > SHAKE_DURATION)
							&& ++shakeCount == SHAKE_COUNT) {
						mLastShake = now;
						if (mShakeNotifierListener != null) {
							mShakeNotifierListener.onShakeDetected(force);
						}
					}
					mLastTime = now;
				}
			}
			break;

		default:

			break;
		}
	}

	private Handler mHandler = new Handler();

	/**
	 * Started when the devices begins to shake. If no shake is performed, the counter 
	 * should reset
	 */
	private Runnable resetShakeCount = new Runnable() {

		public void run() {
			shakeCount = 0;
			shakeStarted = false;
		}
	};

}
